package com.zwh.se.collection.concurrent.blocking_queue;

import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author yexi
 * 本demo中为通过Linked(链表)所实现的阻塞队列。
 * LinkedBlockingQueue：一个可选择的有边界的队列,意思就是队列的长度可以指定,也可以不指定。
 * LinkedBlockingQueue 支持读写同时操作。并发情况下，效率高。底层基于链表。
 */
@SuppressWarnings({"rawtypes", "unchecked"})
public class LinkedBlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        //所谓长度可以不指定,指的其实是空参构造器赋了一个默认值：this(Integer.MAX_VALUE);
        LinkedBlockingQueue lbq = new LinkedBlockingQueue();
        //创建一个队列，队列可以指定容量指定长度3：
        LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue(3);

        System.out.println("--------------------[1]新增操作--------------------");
        //记住阻塞队列不可以添加null元素，会报空指针异常
//        linkedBlockingQueue.add(null);
//        linkedBlockingQueue.offer(null);
//        linkedBlockingQueue.put(null);
        //正常添加
        linkedBlockingQueue.add("aaa");
        linkedBlockingQueue.offer("bbb");
        linkedBlockingQueue.put("ccc");
        //[aaa, bbb, ccc]
        System.out.println(linkedBlockingQueue);
        //在队列满的情况下,再添加元素：
        //1、报错
        try {
            linkedBlockingQueue.add("ddd");
        } catch (Exception e) {
            System.out.println("\033[31;4m" + e + "\033[0m");
        }
        //2、返回操作结果
        System.out.println(linkedBlockingQueue.offer("ddd"));
        //3、阻塞(直至有其他线程将阻塞队列里的元素出队)
        Thread thread = new Thread(() -> {
            try {
                //该段代码模拟其他线程各类操作所消耗的时长
                Thread.sleep(2000);
                //3秒后从阻塞队列中出队一个元素
                linkedBlockingQueue.poll();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        thread.start();
        //直至上方的子线程将元素出队前,将一直处于堵塞状态
        linkedBlockingQueue.put("ddd");
        System.out.println(linkedBlockingQueue);
        //4、设置最大阻塞时间，如果时间到了，队列还是满的，就不再阻塞了
        System.out.println(linkedBlockingQueue.offer("ddd", 2, TimeUnit.SECONDS));

        System.out.println("--------------------源码解析--------------------");
        /**
         * 重要属性
         * @see LinkedBlockingQueue.Node 底层链表节点对应的类
         * @see LinkedBlockingQueue.Node#item 封装要装的那个元素
         * @see LinkedBlockingQueue.Node#next 下一个Node节点的地址
         * @see LinkedBlockingQueue#capacity 容量限制,如果未指定,则为 Integer.MAX_VALUE
         * @see LinkedBlockingQueue#count 当前元素数量
         * @see LinkedBlockingQueue#head 链表的头结点
         * @see LinkedBlockingQueue#last 链表的尾结点
         * @see LinkedBlockingQueue#takeLock 取元素用的锁
         * @see LinkedBlockingQueue#notEmpty 等待池
         * @see LinkedBlockingQueue#putLock  放元素用的锁——通过分开两把锁从而支持读写同时操作
         * @see LinkedBlockingQueue#notFull  等待池
         *
         * @see LinkedBlockingQueue#LinkedBlockingQueue(int capacity)
         *         if (capacity <= 0) throw new IllegalArgumentException(); //健壮性考虑
         *         this.capacity = capacity;  //给队列指定长度
         *         last = head = new Node<E>(null);  //last、head初始化时均指向一个元素为null的节点,也就是初始化时头节点和尾结点是同一个
         *
         */
        lbq = new LinkedBlockingQueue(4);
        /**
         * //在此队列的尾部插入指定元素,如果队列已满,等待直至空间可用
         * @see LinkedBlockingQueue#put(Object e)
         *         if (e == null) throw new NullPointerException(); //健壮性考虑
         *         int c = -1;  //预设值c初始化为-1
         *         Node<E> node = new Node<E>(e);  //将传入的元素封装为Node对象
         *         final ReentrantLock putLock = this.putLock;  //获取放入元素锁
         *         final AtomicInteger count = this.count;   //获取计时器
         *         putLock.lockInterruptibly();   //开锁
         *         try {
         *             //如果判断出当前元素数量 == 容量限制,也就是队列是满的,则当前线程进入notFull队列中等待
         *             while (count.get() == capacity) {
         *                 notFull.await();
         *             }
         *             enqueue(node); //基础的入队方法
         *             c = count.getAndIncrement(); //获取计数赋值给c,并且计数+1
         *             if (c + 1 < capacity)  //c+1实际就是当前队列中节点的数量(元素数量),如果小于capacity就意味着队列还没满
         *                 notFull.signal();  //在没满的时候,就可以激活notFull队列中的其他线程
         *         } finally {
         *             putLock.unlock();  //关锁
         *         }
         *         if (c == 0)  //首先从上面看到预设值是-1。一旦放入元素,c就变为0,c变为0就意味着队列不是空的了,则可以激活notEmpty队列通知可以取元素了
         *             signalNotEmpty();
         *
         * @see LinkedBlockingQueue#enqueue(LinkedBlockingQueue.Node node) 基础的入队方法
         *        //将当前节点用作前一个尾节点的下一个节点,并作为新的尾节点来使用
         *        // TIPS:第一次插入时就相当于 last = head.next = node
         *        last = last.next = node;
         *
         */
        lbq.put("test1");
        /**
         * @see LinkedBlockingQueue#dequeue() 基础的出队方法
         *         Node<E> h = head;  //h指向头节点(头节点存储的元素为null)
         *         Node<E> first = h.next;  //first指向头节点的下一个节点(也就是要取出的节点)
         *         h.next = h; // help GC   // 头节点的h.next指向自己,从而更容易被GC发现,被GC(因为没有多余的属性了)
         *         head = first;   //将原头节点的下一个节点(也就是要取出的节点)用作新的头节点
         *         E x = first.item;  //取出元素
         *         first.item = null; //将头节点的元素设为null(此时要取出的节点已经是新的头节点了)
         *         return x;
         *
         */
        System.out.println(lbq.poll());

    }
}
